<?php
namespace HC\Swoole;

use HC\Swoole\Exception\ClientException;

/**
 * Socket Client 类.
 */
class Client
{
    /**
     * @var \Swoole_Client
     */
    protected $socket;

    /**
     * @var string
     */
    protected $address = '127.0.0.1';

    /**
     * @var int
     */
    protected $port;

    /**
     * swoole 设置
     * https://wiki.swoole.com/wiki/page/p-client_setting.html
     * @var array
     */
    protected $options = [
        'open_eof_check'     => true,
        'package_eof'        => "\r\n\r\n",
        'package_max_length' => 1024 * 1024,  // 1M
    ];

    /**
     * @param int $port
     * @param array $options
     * @param int $timeout
     * @throws
     */
    public function __construct(int $port = 9651, array $options = [], int $timeout = 3)
    {
        $this->port    = $port;
        $this->options = $options ? array_merge($this->options, $options) : $this->options;

        $this->socket = new \Swoole_Client(SWOOLE_SOCK_UDP, SWOOLE_SOCK_SYNC);
        $this->socket->set($options);
        $this->socket->connect($this->address, $this->port, $timeout);

        if (!$this->isConnected()) {
            throw new ClientException('socket connect fail');
        }
    }

    /**
     * @return bool
     */
    public function isConnected() : bool
    {
        if (!$this->socket) {
            return false;
        }

        try {
            $fp = @fsockopen($this->address, $this->port, $no, $error, 1);
        } catch (\Throwable $e) {
            return false;
        }

        return $fp ? true : false;
    }

    /**
     * @param string $class
     * @param array $args
     * @param bool $sync
     * @return bool|mixed
     * @throws
     */
    public function exec(string $class, array $args = [], bool $sync = true)
    {
        \HC\Swoole\sw_class_exists($class);

        return $this->send($class, $args, $sync);
    }

    /**
     * @param string $cmd
     * @param array $args
     * @param bool $sync
     * @return bool|mixed
     * @throws
     */
    protected function send(string $cmd, array $args = [], bool $sync = true)
    {
        $value = [
            'cmd'  => $cmd,
            'args' => $args,
            'time' => time(),
        ];

        if ($this->socket->send(\HC\Swoole\sw_json_encode($value)) === false) {
            return false;
        }

        if ($sync) {
            return true;
        }

        $result = $this->socket->recv();
        if ($result === false) {
            throw new Exception\ClientException('socket receive error');
        }

        return \HC\Swoole\sw_json_decode($result);
    }
}
